<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <input type="text" v-model="msg" id="a">
        {{msg}}
    </div>
    <script>
        //分析：
        // 1、将vue中的data中的数据绑定到表单上边和文本节点上
        // 2、当表单的值改变时，vue实例中的data也同时发生变化
        // 3、当data中的数据发生变化时，输入框和文本节点的值也会发生变化

        // 数据发生变化，视图跟着变化，视图变化了数据也随之变化
        // vue.js 是采用数据劫持结合发布者-订阅者模式的方式，通过Object.defineProperty()来劫持各个属性的setter，getter，在数据变动时发布消息给订阅者，触发相应的监听回调。

        //重绘和回流？
        // 重绘：只改变自身样式，不会影响到其他元素
        // 例如：color，backgroundColor，visibility，opacity

        // 回流：元素的大小或者位置发生变化（页面布局发生变化时）
        // 例如：
        // 1、width,height或者高度改变
        // 2、动态添加dom元素

        //回流一定会重绘，重绘不一定回流

        // DocumentFragment 碎片化文档
        // document.createDocumentFragment()  创建一个碎片化文档（容器）
        // 劫持数据放在碎片化文档中

        //定义一个编译函数compile---一旦数据有变动，收到通知，更新视图
        function compile(node, vm) {
            // 定义一个正则表达式 {{xxx}}中的xxx
            // {{msg}} 定义一个正则来找出msg
            // 量词 *匹配0个或者任意多个   ？匹配0个或者1个    +匹配1个或者任意多个
            var reg = /\{\{(.*)\}\}/
            //节点类型  元素节点、文本节点、属性节点
            //节点的值   nodeType  元素节点=1   属性节点=2  文本节点=3
            //属性节点集合 attributes
            // console.log(document.getElementById("a").attributes);
            if (node.nodeType === 1) {
                //如果条件成立说明node时元素节点
                //如果node是 <input type="text" v-model="msg" id="a">
                var attr = node.attributes  //获得node节点上的属性集合
                // console.log(attr);

                // console.log(attr[2].nodeName);
                //attr =['type','v-model','id']
                for (let i = 0; i < attr.length; i++) {
                    if (attr[i].nodeName == 'v-model') {
                        var name = attr[i].nodeValue
                        // console.log(name);
                        // console.log(vm);
                        console.log(node); //<input type="text" v-model="msg" id="a">
                        node.addEventListener("input", function (e) {
                            // console.log(e.target.value); //表单中输入的值

                            // console.log(name); //msg
                            vm.data[name] = e.target.value
                            console.log(vm);  //实例对象
                        })
                        node.value = vm.data[name] //将data中的值赋值给node
                    }
                }
                // console.log(vm);
            }
            if (node.nodeType == 3) {
                //条件成立node是文本节点
                // {{ msg }} 
                //test()
                //exec()
                // console.log(321);

                if (reg.test(node.nodeValue)) {
                    var name = RegExp.$1
                    // console.log(name); //msg
                    name = name.trim()  //去掉首尾空格
                    node.nodeValue = vm.data[name]
                    //初始化观察者
                    new Watcher(vm, node, name)
                }
            }
        }

        //定义一个函数响应式的监听属性的变化
        // Object.defineProperty()
        function defineReactive(obj, key, val) {
            var dep = new Dep()
            Object.defineProperty(obj, key, {
                get: function () {
                    if (Dep.target) {
                        dep.addSub(Dep.target)
                    }
                    return val
                },
                set: function (newVal) {
                    if (val == newVal) {
                        return
                    }
                    val = newVal
                    console.log("新值----" + val);
                    //要执行通知
                    dep.notify()
                }
            })
        }

        //观察者函数
        function observe(obj, vm) {
            //obj指的是Vue实例中的data
            console.log(obj); //{msg: "好谷"}
            for (let key of Object.keys(obj)) {
                // console.log(key);
                defineReactive(vm.data, key, obj[key])
            }

        }


        //订阅者和发布者关系
        //发布者和订阅者模式又称为观察者模式，是一对多的关系
        //一个发布者可以对应多个订阅者(观察者)
        //让多个订阅者(观察者)同时监听某一个主题对象，如果这个主题的状态发生变化，就会通知订阅者(观察者)

        // 如： 河南疾控中心发布新冠病毒感染人数---河南疾控中心属于发布者
        //      全省人们属于订阅者(观察者)

        // 观察者
        function Watcher(vm, node, name) {
            console.log(node);
            console.log(name);
            console.log(vm);
            Dep.target = this;  //给主题构造函数添加一个全局变量

            this.vm = vm
            this.node = node
            this.name = name
            this.update()
            Dep.target = null;
        }
        //通过字面量的形式添加原型方法
        Watcher.prototype = {
            update: function () {
                this.get()
                this.node.nodeValue = this.value
            },
            get() {
                this.value = this.vm.data[this.name]
            }
        }

        //定义一个主题函数
        function Dep() {
            this.subs = []
        }
        Dep.prototype = {
            addSub(sub) {  //添加订阅者
                this.subs.push(sub)
            },
            notify() { //执行通知
                this.subs.forEach(function (sub) {
                    sub.update()
                })
            }
        }

        function nodeFragment(node, vm) {
            var fragment = document.createDocumentFragment() // 创建一个碎片化文档（容器）
            var child;  //指的是子节点
            while (child = node.firstChild) {
                // console.log(child);

                compile(child, vm)  //执行编译函数
                //判断是否有子节点，直到child为undefined，循环结束
                fragment.appendChild(child)
                //appendChild将节点插入到碎片化文档中，原来的节点会被删除
            }
            return fragment
        }
        // var dom = nodeFragment(document.getElementById("app"))
        // console.log(dom);

        // 自定义一个Vue构造函数
        function Vue(options) {
            this.data = options.data;  //{msg:'haogu'}
            this.id = options.el;      //#app
            //执行观察者函数
            observe(this.data, this)
            //碎片化文档容器-劫持到了所有的节点
            var dom = nodeFragment(document.querySelector(this.id), this)

            //将节点一次性添加到#app这个div中，目的只回流一次
            document.querySelector(this.id).appendChild(dom)
        }

        //初始化一个vue实例对象
        var vm = new Vue({
            el: "#app",
            data: {
                msg: "123456",
                age: 18,
                sex: "男"
            }
        })




    //总结：
    //1、分析双向数据绑定的表面效果
        // 1、将vue中的data中的数据绑定到表单上边和文本节点上
        // 2、当表单的值改变时，vue实例中的data也同时发生变化
        // 3、当data中的数据发生变化时，输入框和文本节点的值也会发生变化
    //2、重绘和回流---提高执行效率---避免多次回流
        //DocumentFragment  碎片化文档劫持所有的DOM节点，对节点逐个处理，最后统一添加到#app这个标签中








    </script>
</body>

</html>